package org.apache.ocean.main.client;

import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.net.URL;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.logging.Logger;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.ocean.ClusterUtil;
import org.apache.ocean.main.http.HttpParameters;
import org.apache.ocean.main.http.HttpRequest;
import org.apache.ocean.main.http.HttpTimeout;
import org.apache.ocean.main.util.CElement;
import org.apache.ocean.main.util.NamedList;
import org.apache.ocean.main.util.XMLUtil;
import org.apache.ocean.main.util.Timeout.TimeoutException;
import org.apache.ocean.main.util.XMLUtil.XMLException;
import org.jdom.Element;

/**
 * 
 * @author jasonr
 */
// TODO: add tostring method
public class OceanXML implements CElement {
	public static Logger log = Logger.getLogger(OceanXML.class.getName());
	private NamedList<String, Element> headers = new NamedList<String, Element>();
	private Element bodyElement;
	private Date timestamp;
	public static ThreadLocal<Boolean> logThreadLocal = new ThreadLocal<Boolean>();

	public OceanXML() {
	}

	public static void main(String[] args) {
		try {

			/**
			 * ServerInfo serverInfo = new ServerInfo(); for (Field field :
			 * FieldUtil.getFields(ServerInfo.class)) { log.info(field.getName()); }
			 * FieldUtil.setFieldValue(serverInfo, "id", "id sdfsdfsdf");
			 * log.info(serverInfo.toString());
			 */
		} catch (Throwable throwable) {
			throwable.printStackTrace();
		}
	}

	public boolean getHeaderBoolean(String name) {
		String value = getHeaderValue(name);
		return BooleanUtils.toBoolean(value);
	}

	public URL getHeaderURL(String name) throws Exception {
		return ClusterUtil.parseURL(getHeaderValue(name));
	}

	public Integer getHeaderInt(String name) throws ParseException {
		String value = getHeaderValue(name);
		if (StringUtils.isBlank(value))
			return null;
		return new Integer(value);
	}

	public Long getHeaderLong(String name) throws ParseException {
		String value = getHeaderValue(name);
		if (StringUtils.isBlank(value))
			return null;
		return new Long(value);
	}

	public static class ServerInfo implements CElement {
		public String id;
		public String type;
		public String reportableStatus;

		public ServerInfo() {
		}

		public String toString() {
			return ReflectionToStringBuilder.toString(this);
		}

		public ServerInfo(String id, String type, String reportableStatus) {
			this.id = id;
			this.type = type;
			this.reportableStatus = reportableStatus;
		}

		public Element toElement() throws Exception {
			return XMLUtil.reflectToElement("serverinfo", this);
		}

		public ServerInfo(Element element) throws Exception {
			XMLUtil.reflectToObject(this, element);
		}
	}

	public void setServerInfo(ServerInfo serverInfo) throws Exception {
		headers.add("serverinfo", serverInfo.toElement());
	}

	public ServerInfo getServerInfo() throws Exception {
		Element element = headers.get("serverinfo");
		if (element == null)
			return null;
		return new ServerInfo(element);
	}

	public OceanXML(String... headerStrings) {
		for (int x = 0; x < headerStrings.length; x = x + 2) {
			String name = headerStrings[x];
			String value = headerStrings[x + 1];
			addHeader(name, value);
		}
	}

	public boolean isAction(String other) {
		String action = getHeaderValue("action");
		return StringUtils.equals(other, action);
	}

	public void addHeader(String name, Object value) throws Exception {
		if (value != null) {
			if (value instanceof Element) {
				headers.add(name, (Element) value);
				return;
			} else if (value instanceof CElement) {
				headers.add(name, ((CElement) value).toElement());
				return;
			}
			Element element = new Element(name);
			if (value instanceof String) {
				element.setText((String) value);
			} else {
				element.setText(value.toString());
			}
			headers.add(name, element);
		}
	}

	public void addHeader(String name, String value) {
		if (value != null) {
			Element element = new Element(name);
			element.setText(value);
			headers.add(name, element);
		}
	}

	public String getHeaderValue(String name) {
		Element element = headers.get(name);
		if (element != null) {
			return element.getText();
		} else {
			return null;
		}
	}

	public static class RequestException extends Exception {
		public Object attachment;
		public URL url;
		public OceanXML oceanResponse;

		public RequestException(Object attachment, URL url, String msg) {
			super(url + " " + msg);
			this.attachment = attachment;
			this.url = url;
		}

		public RequestException(Object attachment, URL url, String msg, OceanXML oceanResponse) {
			super(url + " " + msg);
			this.attachment = attachment;
			this.url = url;
			this.oceanResponse = oceanResponse;
		}

		public RequestException(String msg) {
			super(msg);
		}
	}

	public static class UpdateException extends RequestException {
		public UpdateException(String msg) {
			super(msg);
		}
	}

	public static class SearchException extends RequestException {
		public SearchException(String msg) {
			super(msg);
		}
	}

	public static class ReflectTransform implements Transform {
		Class clazz;

		public ReflectTransform(Class clazz) {
			this.clazz = clazz;
		}

		public Object call(Element element) throws Exception {
			return ClusterUtil.construct(element, clazz);
		}
	}

	public static interface Transform {
		public Object call(Element element) throws Exception;
	}

	public Element getFirstInBody() throws Exception {
		return XMLUtil.getFirstChild(bodyElement);
	}

	public Collection<Element> getBodyChildren() throws Exception {
		return XMLUtil.getChildren(bodyElement);
	}

	public Object getFirstInBody(Transform transform) throws Exception {
		Element element = XMLUtil.getFirstChild(bodyElement);
		if (element == null)
			return null;
		return transform.call(element);
	}

	public List bodyToList(Transform transform) throws Exception {
		List list = new ArrayList();
		if (bodyElement != null) {
			for (Element element : XMLUtil.getChildren(bodyElement)) {
				list.add(transform.call(element));
			}
		}
		return list;
	}

	public static void write(ServerInfo serverInfo, Element element, Writer writer) throws Exception {
		List bodyList = new ArrayList(1);
		bodyList.add(element);
		write(serverInfo, bodyList, writer, null);
	}

	public static void write(ServerInfo serverInfo, CElement cElement, Writer writer) throws Exception {
		write(serverInfo, cElement, writer, null);
	}

	public static void write(ServerInfo serverInfo, CElement cElement, Writer writer, Logger logger) throws Exception {
		List bodyList = new ArrayList(1);
		bodyList.add(cElement);
		write(serverInfo, bodyList, writer, logger);
	}

	public static void write(ServerInfo serverInfo, Collection bodyList, Writer writer) throws Exception {
		write(serverInfo, bodyList, writer, null);
	}

	public static void write(ServerInfo serverInfo, NamedList<String, Object> headers, Collection bodyList, Writer writer) throws Exception {
		write(serverInfo, bodyList, writer, null);
	}

	public static void write(ServerInfo serverInfo, Collection bodyList, Writer writer, Logger logger) throws Exception {
		OceanXML oceanXML = new OceanXML();
		oceanXML.addHeader(serverInfo.toElement());
		oceanXML.bodyElement = new Element("body");
		for (Object object : bodyList) {
			if (object != null) {
				if (object instanceof CElement) {
					oceanXML.addBody(((CElement) object).toElement());
				} else {
					if (object instanceof Element) {
						oceanXML.addBody((Element) object);
					}
				}
			}
		}
		String xml = XMLUtil.outputElement(oceanXML.toElement());
		if (logger != null)
			logger.info(xml);
		writer.write(xml);
	}

	public static String toOceanXMLString(Throwable throwable) throws Exception {
		OceanXML oceanXML = new OceanXML();
		oceanXML.addHeader("hasException", "true");
		String stackTrace = ExceptionUtils.getFullStackTrace(throwable);
		oceanXML.addHeader("exceptionMessage", throwable.getMessage());
		oceanXML.addHeader("exceptionStacktrace", stackTrace);
		return XMLUtil.outputElement(oceanXML.toElement());
	}

	public static void throwRequestException(OceanXML oceanXML) throws RequestException {
		throwRequestException(null, null, oceanXML);
	}

	public static void throwRequestException(Object attachment, URL url, OceanXML oceanXML) throws RequestException {
		if (ClusterUtil.isTrue(oceanXML.getHeaderValue("hasException"))) {
			String exceptionStacktrace = oceanXML.getHeaderValue("exceptionStacktrace");
			throw new RequestException(attachment, url, exceptionStacktrace, oceanXML);
		}
	}

	public static OceanXML request(URL url, OceanXML oceanRequest, HttpTimeout httpTimeout) throws Exception {
		String requestXML = XMLUtil.outputElement(oceanRequest.toElement());
		String xml = HttpRequest.post(url, "UTF-8", requestXML.getBytes("UTF-8"), httpTimeout).getBodyString();
		if (doLog()) {
			log.info(xml);
		}
		Element element = XMLUtil.parseElement(xml);
		OceanXML response = new OceanXML(element);
		throwRequestException(response);
		return response;
	}

	public static boolean doLog() {
		Boolean logBoolean = logThreadLocal.get();
		if (logBoolean != null && logBoolean.booleanValue()) {
			return true;
		}
		return false;
	}

	public static OceanXML request(URL url, HttpParameters parameters, HttpTimeout httpTimeout) throws TimeoutException, RequestException, ParseException, XMLException, IOException {
		String xml = HttpRequest.get(url, parameters, httpTimeout).getBodyString();
		if (doLog()) {
			log.info(xml);
		}
		Element element = XMLUtil.parseElement(xml);
		OceanXML oceanXML = new OceanXML(element);
		throwRequestException(null, url, oceanXML);
		return oceanXML;
	}

	public void addHeader(Element element) {
		headers.add(element.getName(), element);
	}

	public void addBodyAll(Collection values) throws Exception {
		if (bodyElement == null) {
			bodyElement = new Element("body");
		}
		for (Object object : values) {
			if (object instanceof CElement) {
				addBody((CElement) object);
			} else if (object instanceof Element) {
				addBody((Element) object);
			}
		}
	}

	public void addBody(CElement element) throws Exception {
		if (bodyElement == null) {
			bodyElement = new Element("body");
		}
		bodyElement.addContent(element.toElement());
	}

	public void addBody(Element element) {
		if (bodyElement == null) {
			bodyElement = new Element("body");
		}
		bodyElement.addContent(element);
	}

	public Element getHeaderElement(String name) {
		return headers.get(name);
	}

	public static OceanXML handle(Reader reader) throws Exception {
		OceanXML response = parse(reader);
		throwRequestException(response);
		return response;
	}

	public static OceanXML parse(Reader reader) throws Exception {
		String xml = IOUtils.toString(reader);
		Element root = XMLUtil.parseElement(xml);
		return new OceanXML(root);
	}

	public OceanXML(Element ocean) throws ParseException {
		timestamp = XMLUtil.getAttributeDate("timestamp", ocean);
		for (Element element : XMLUtil.getChildren(ocean)) {
			if (element.getName().equals("body")) {
				bodyElement = element;
			} else {
				headers.add(element.getName(), element);
			}
		}
	}

	public void write(Writer writer) throws Exception {
		writer.write(XMLUtil.outputElement(toElement()));
	}

	public Element toElement() {
		Element root = new Element("ocean");
		XMLUtil.setAttribute("timestamp", new Date(), root);
		root.addContent(headers.getValues());
		// XMLUtil.addAll(headers.getValues(), root);
		if (bodyElement != null)
			root.addContent(bodyElement);
		return root;
	}
}
